import { IPlankListParams, bomApi } from '@/api';
import * as fromOutApi from '@/api/from-out';
import { Basis } from '@/components/Editor/constants';
import { EditType } from '@/components/Editor/types';
import { isDev } from '@/config/env';
import { mockBom } from '@/mock';
import { Groove, Hole, IPlank, Order } from '@/types';
import { useHistoryTravel } from 'ahooks';
import { message } from 'antd';
import findReplace from 'find-replace';
import {
  cloneDeep,
  defaults,
  difference,
  differenceBy,
  intersectionBy,
  isEmpty,
  merge,
  omit,
  pick,
} from 'lodash-es';
import { useEffect, useRef, useState } from 'react';

const log = console.log.bind(null, '[model:bom]');

export default function bom() {
  // @ts-expect-error
  const [order, setOrder] = useState<Order>(isDev ? mockBom.order : undefined);
  const [planks, setPlanks] = useState<IPlank[]>(
    isDev ? [...mockBom.planks] : [],
  );
  const {
    value: plank,
    setValue: setPlank,
    backLength,
    back,
    forwardLength,
    forward,
    reset,
  } = useHistoryTravel<IPlank>();
  const [holeId, setHoleId] = useState<string>();
  const [hoveredHoleId, setHoveredHoleId] = useState<string>();
  const [grooveId, setGrooveId] = useState<string>();
  const [hoveredGrooveId, setHoveredGrooveId] = useState<string>();
  const [hoveredPlank, setHoveredPlank] = useState<boolean>();
  const [basis, setBasis] = useState<{ x: Basis; y: Basis }>();
  /** 适配外部参数注入 */
  const fromOutObj = {
    init() {
      fromOutApi.getParams().then(async (data) => {
        log('外部参数:', data);
        const { orderId, mergeId, plank } = data;
        if (plank) {
          setPlank(plank);
          return;
        }
        if (orderId) {
          const {
            result: [order],
          } = await Manycore.Integration.FOP.findOrdersAsync({
            orderIds: [orderId],
          });
          setOrder(order);
          refreshPlanks({ orderId });
          return;
        }
        if (mergeId) {
          refreshPlanks({ mergeId });
          return;
        }
      });
    },
  };
  useEffect(() => {
    fromOutObj.init();
  }, []);
  async function refreshPlanks(params: IPlankListParams) {
    return await bomApi.getPlankList(params).then((res) => {
      const planks = res.result;
      log('获取板件数据:', planks);
      if (isEmpty(planks)) {
        message.warning('板件数据为空，请检查订单信息');
      }
      setPlanks(planks);
      return planks;
    });
  }
  const oldPlanksRef = useRef<IPlank[]>([]);
  useEffect(() => {
    oldPlanksRef.current = cloneDeep(planks);
  }, [planks]);
  function getOldPlank(plankId: string) {
    return planks.find((plank) => plank.plankId === plankId)!;
  }
  function setOldPlank(newPlank: IPlank) {
    findReplace(
      planks,
      (plank) => plank.plankId === newPlank.plankId,
      newPlank,
    );
    setPlanks([...planks]);
  }
  async function updatePlank(plankData: IPlank, editTypes: EditType[]) {
    const { plankId } = plankData;
    const oldPlank = getOldPlank(plankId);
    const oldHoles = oldPlank.holes;
    const oldGrooves = oldPlank.grooves;
    // setOldPlank(plankData);

    const isPlankEdited = editTypes.includes(EditType.Plank);
    if (isPlankEdited) {
      const undatedPlank = pick(plankData, 'plankId');
      if (plankData.finishedProfile) {
        merge(undatedPlank, pick(plankData, 'finishedProfile'));
      } else {
        merge(undatedPlank, pick(plankData, 'finishedWidth', 'finishedHeight'));
      }
      await Manycore.Integration.Bom.updatePlankAsync(undatedPlank);
    }
    if (isPlankEdited || editTypes.includes(EditType.Hole)) {
      const groupedHoles = groupedHolesByAction(plankData.holes, oldHoles);
      await Promise.all(
        groupedHoles[Action.Update]
          .map(async (hole) => {
            return Manycore.Integration.Bom.Plank.updateHoleAsync(hole);
          })
          .concat(
            !isEmpty(groupedHoles[Action.Create])
              ? Manycore.Integration.Bom.Plank.createHolesAsync({
                  holes: groupedHoles[Action.Create].map((hole) => {
                    return defaults(
                      omit(hole, 'holeId'),
                      { plankId },
                      getHoleDefaultValue(),
                    );
                  }),
                })
              : [],
          )
          .concat(
            groupedHoles[Action.Delete].map((hole) => {
              return Manycore.Integration.Bom.Plank.deleteHoleAsync(
                hole.holeId,
              );
            }),
          ),
      );
    }
    if (isPlankEdited || editTypes.includes(EditType.Groove)) {
      const groupedGrooves = groupedGrooveByAction(
        plankData.grooves,
        oldGrooves,
      );
      await Promise.all(
        groupedGrooves[Action.Update]
          .map(async (groove) => {
            // @ts-ignore
            // groove has plankId
            return Manycore.Integration.Bom.Plank.updateGrooveAsync(
              omit(groove, 'geometryType'),
            );
          })
          .concat(
            !isEmpty(groupedGrooves[Action.Create])
              ? Manycore.Integration.Bom.Plank.createGroovesAsync({
                  grooves: groupedGrooves[Action.Create].map((groove) => {
                    return defaults(
                      omit(groove, 'grooveId'),
                      { plankId },
                      getGrooveDefaultValue(),
                    );
                  }),
                })
              : [],
          )
          .concat(
            groupedGrooves[Action.Delete].map((groove) => {
              return Manycore.Integration.Bom.Plank.deleteGrooveAsync(
                groove.grooveId,
              );
            }),
          ),
      );
    }
    const {
      result: [newPlank],
    } = await Manycore.Integration.Bom.findPlankListAsync({
      plankIds: [plankData.plankId],
    });
    if (newPlank) {
      setPlank(newPlank);
      setOldPlank(newPlank);
    } else {
      console.error('updated plank not found');
    }
  }

  function initPlank() {
    // setPlank(planks[0]);
  }

  function isPlankExist() {
    const plankId = plank?.plankId;
    if (!plankId) return;
    return planks.some((plankData) => {
      return plankData.plankId === plankId;
    });
  }

  useEffect(() => {
    !isPlankExist() && initPlank();
  }, [planks]);

  return {
    order,
    planks,
    setPlanks,
    plank,
    setPlank,
    getOldPlank,
    backLength,
    back,
    forwardLength,
    forward,
    reset,
    holeId,
    setHoleId,
    hoveredHoleId,
    setHoveredHoleId,
    grooveId,
    setGrooveId,
    hoveredGrooveId,
    setHoveredGrooveId,
    hoveredPlank,
    setHoveredPlank,
    refreshPlanks,
    updatePlank,
    basis,
    setBasis,
  };
}

enum Action {
  Create = 'create',
  Update = 'update',
  Delete = 'delete',
}

function getDefaultActionValue() {
  return {
    [Action.Create]: [],
    [Action.Update]: [],
    [Action.Delete]: [],
  };
}

function groupedHolesByAction(
  holes: Hole[],
  oldHoles: Hole[],
): Record<Action, Hole[]> {
  difference(
    holes.map((hole) => hole.holeId),
    oldHoles.map((hole) => hole.holeId),
    'holeId',
  );
  return defaults(
    {
      [Action.Update]: intersectionBy(holes, oldHoles, 'holeId'),
      [Action.Create]: differenceBy(holes, oldHoles, 'holeId'),
      [Action.Delete]: differenceBy(oldHoles, holes, 'holeId'),
    },
    getDefaultActionValue(),
  );
}

function groupedGrooveByAction(
  groove: Groove[],
  oldGrooves: Groove[],
): Record<Action, Groove[]> {
  return defaults(
    {
      [Action.Update]: intersectionBy(groove, oldGrooves, 'grooveId'),
      [Action.Create]: differenceBy(groove, oldGrooves, 'grooveId'),
      [Action.Delete]: differenceBy(oldGrooves, groove, 'grooveId'),
    },
    getDefaultActionValue(),
  );
}
function getHoleDefaultValue() {
  return { type: 'PLANK', name: 'hole' };
}
function getGrooveDefaultValue() {
  return { type: 'PLANK', name: 'groove' };
}
